#pragma once

#define GLM_PRECISION_HIGHP_FLOAT
#define GLM_PRECISION_HIGHP_DOUBLE
#define GLM_PRECISION_HIGHP_INT

#include "../GameLogic/BaseGameLogic.h"
#include "../Utility/types.h"

#include <glew.h>
#include <GLFW\glfw3.h>
#include <CL\cl.h>
#include <CL\cl_gl.h>
#include <memory>
#include <iostream>
#include <fstream>

class Scene;

class Application
{
	friend class BaseGameLogic;

private:
	std::ofstream logFile;
	std::ofstream clLogFile;
	std::ofstream glLogFile;

	//void InitializeWindow();

protected:
	Rect windowRect;
	int windowWidth, windowHeight;
	int miColorDepth;
	GLFWwindow* mainWindow;

	BaseGameLogic* mpGame;
	Scene* mpScene;

	// OpenCl
	cl_device_id* mclDevices;
	cl_context mclContext;
	cl_program mclProgram;
	cl_command_queue mclgpuQueue;
	cl_command_queue mclcpuQueue;

	// OpenGl


	virtual char* vGetGameTitle() = 0;
	//virtual TCHAR* vGetGameAppDirectory() = 0;
	//virtual TCHAR* vGetZipFileName() = 0;
	virtual HICON vGetIcon() = 0;
	virtual HCURSOR WINAPI vLoadCursor() = 0;

public:
	Application();
	~Application();

	//void error_callback( int error, const char* description );
	bool Initialize( HINSTANCE hInstance);

	void OnUpdate( double dTime, double dElapsedTime );
	void OnRender();

	//OpenCL stuff
	virtual BaseGameLogic* vCreateGame() = 0;

	inline cl_mem CreateClBuffer(const cl_mem_flags& options, const size_t& size, void* host_ptr, cl_int* error)
	{
		return clCreateBuffer(mclContext, options, size, host_ptr, error);
	}
	inline cl_int EnqueueNDKernel(const cl_kernel& kernel, cl_uint work_dims, const size_t* global_work_offset, const size_t* global_work_size, const size_t* local_work_size,
		cl_uint num_events, const cl_event* wait_list, cl_event* event)
	{
		return clEnqueueNDRangeKernel(mclgpuQueue, kernel, work_dims, global_work_offset, global_work_size, local_work_size, num_events, wait_list, event);
	}
	inline cl_int ReadClBuffer( cl_mem& buffer, cl_bool blocking, size_t offset, size_t data_size, void* destination, cl_uint num_events, const cl_event* wait_list, cl_event* event )
	{
		return clEnqueueReadBuffer(mclgpuQueue, buffer, blocking, offset, data_size, destination, num_events, wait_list, event);
	}
	inline cl_int WriteClBuffer(cl_mem& buffer, cl_bool blocking, size_t offset, size_t data_size, void* source, cl_uint num_events, const cl_event* wait_list, cl_event* event)
	{
		return clEnqueueWriteBuffer(mclgpuQueue, buffer, blocking, offset, data_size, source, num_events, wait_list, event);
	}
	inline cl_int FinishClQueue() { return clFinish(mclgpuQueue); }

	// Logging
	void PrintToClLog(const char* msg) { clLogFile << msg; }
	void PrintToClLog(const int msg) { clLogFile << msg; }
	void PrintToGlLog(const char* msg) { glLogFile << msg; }
	void PrintToGlLog(const int msg) { glLogFile << msg; }

	// Getters
	//inline Rect GetWindowRect() { return mrcWindow; }
	inline GLFWwindow* GetMainWindow() { return mainWindow; }
	inline BaseGameLogic* GetLogic() { return mpGame; }
	inline cl_device_id* GetClDevices() { return mclDevices; }
	//inline cl_context GetClContext() { return mclContext; }
	inline const cl_program GetClProgram() { return mclProgram; }
};

extern INT WINAPI GameCode( HINSTANCE hInstance, 
	HINSTANCE hprevInstance, LPWSTR lpCmdLine, int nCmdShow );

extern Application* gpApp;